package datagraph.core

import scala.collection.mutable
import scala.util.Try

trait Engine[R] {
  def exec(g: GraphConf): R

}


class EngineHelper(g: GraphConf) {

  protected def cacheAnyBundleLink(bundle: BundleConf.BasicInputCtl[_]): Cache.CacheAnyBundleLink = bundle.bundle match {


    case b: Bundle.Inputable =>

      Cache.CacheAnyBundleLink(b, (bundle.inputProcesses._1().map { case b: BundleConf.BasicInputCtl[_] =>
        cacheAnyBundleLink(b)
      }))
    case b: Bundle.OnlyOutput[_] => Cache.CacheAnyBundleLink(b, Nil)
    case b => throw  ???

  }

  protected def cacheTBundleLink(bundle: BundleConf.BasicInputCtl[_]): Cache.CacheTypeBundleLink =
    bundle match {
      case bundle:BundleConf.OutputCacheLinkCtl[_]=>
        bundle.bundle match {


          case b: Bundle.Inputable =>

            Cache.CacheTypeBundleLink(b, (bundle.inputProcesses._1().map { case b: BundleConf.OutputCacheLinkCtl[_] =>
              cacheTBundleLink(b)
            }),bundle.resultReadWriter.asInstanceOf[ReadWriter[Any]],bundle.bundleReadWriter.asInstanceOf[ReadWriter[Bundle]])
          case b: Bundle.OnlyOutput[Any] => Cache.CacheTypeBundleLink(b, Nil,
            bundle.resultReadWriter.asInstanceOf[ReadWriter[Any]],bundle.bundleReadWriter.asInstanceOf[ReadWriter[Bundle]])
          case b => throw  ???

        }
    }





  protected def getIns[T <: DataGraph](allRegsByBundleConf: Map[BundleConf[_], String], result: mutable.LinkedHashMap[String, Any], conf: BundleConf[_]) = {
    val inputs = conf.asInstanceOf[BundleConf.BasicInputCtl[_]].inputProcesses._1()
    if (inputs.forall { bundleConf =>
      allRegsByBundleConf.get(bundleConf).exists { name => result.contains(name) }
    }) {
      val values = inputs.map { bundleConf =>
        result(allRegsByBundleConf(bundleConf))
      }
      conf.asInstanceOf[BundleConf.BasicInputCtl[_]].inputProcesses._2(values)


    }
    else
      throw ??? //FIXME:用户错误使用？

  }

  val isCacheable = g.isInstanceOf[GraphConf.Cacheable]

  val isGlobalCacheEnable =
    if (isCacheable)
      Try {
        g.asInstanceOf[GraphConf.Cacheable].defaultEnableCache
      }.getOrElse(false)
    else false


  val allRegsByKey = g.getAllRegisBundles
  val allRegsByBundleConf = allRegsByKey.map {
    _.swap
  }.toMap
  val result = collection.mutable.LinkedHashMap[String, Any]()

  def byBundle(name: String, conf: BundleConf[_]) = new {



    val enableBundleCache = conf match {
      case c: BundleConf.Cache[_] => c.enableCache
      case _ => isGlobalCacheEnable
    }
    lazy val  getCache =
      if (enableBundleCache)
        g match{
          case g:GraphConf.CacheAny=>g.get(cacheAnyBundleLink(conf.asInstanceOf[BundleConf.BasicInputCtl[_]]))
          case g:GraphConf.CacheLink=>g.get(cacheTBundleLink(conf.asInstanceOf[BundleConf.BasicInputCtl[_]]))
        }
      else null


    def save(output: Any) = {
      if (enableBundleCache && getCache == null)
        g match{
          case g:GraphConf.CacheAny=>g.save(cacheAnyBundleLink(conf.asInstanceOf[BundleConf.BasicInputCtl[_]]), output)
          case g:GraphConf.CacheLink=>g.save(cacheTBundleLink(conf.asInstanceOf[BundleConf.BasicInputCtl[_]]),output)
        }
      result(name) = output
    }

    def inParams = getIns(allRegsByBundleConf, result, conf)
  }


}


object LocalEngine extends Engine[String Map Any] {
  /*  def exec[T <: DataGraph](g: GraphConf with GraphConf.Cache): Map[String, Any]= {
      val allRegsByKey = g.getRegs.reverse.toMap
      val allRegsByBundleConf = allRegsByKey.map {
        _.swap
      }
      val result = collection.mutable.LinkedHashMap[String, Any]()

      ???

    }*/


  override def exec(g: GraphConf): Map[String, Any] = {
    //FIXME:检查配置 上流是否有配

    val helper = new EngineHelper(g)
  import helper._

    allRegsByKey.filter{_._2.enable}.foreach { case (name, conf) =>
      val bundle = conf.bundle
      val bundleHelper = byBundle(name, conf)

      if (!result.contains(name))
        bundle match {
          case b: Bundle.Outputable =>

            val cache = bundleHelper.getCache

            val output = cache match {
              case null => b match {
                case b: Bundle.OnlyOutput[_] => b()
                case b: Bundle.InAndOut[Any, Any] =>
                  b(bundleHelper.inParams)
              }
              case v => v
            }

            bundleHelper.save(output)


          case b: Bundle.OnlyInput[Any] =>
            b(bundleHelper.inParams)
          case b: Bundle.JustRun =>
            b()
        }

    }
    helper.result.toMap
  }


}



